/**
 * @name CeL function for physical quantities
 * @fileoverview 本檔案包含了 value of a quantity/物理量/度量衡（長度、時間、重量等）標準/國際單位/國際單位制詞頭
 *               單位換算用的 functions。<br />
 * 
 * TODO:<br />
 * https://en.wikipedia.org/wiki/Category:Units_of_amount
 * https://en.wikipedia.org/wiki/Significance_arithmetic
 * 
 * http://thdl.ntu.edu.tw/tools/
 * http://thdl.ntu.edu.tw/weight_measure/transformation8.php<br />
 * 中國度量衡/中國古代貨幣 http://thdl.ntu.edu.tw/suzhou/
 * 
 * @since
 * @example <code>
 * CeL.run('data.quantity',function(){
 * 	// ..
 * });
 * </code>
 * 
 * @see http://unit.0123456789.tw/
 * 
 * @example <code>


cm^2, cm2 視作 cm²

CeL.assert([(new CeL.quantity('5.4cm')).toString(),'5.4 cm'],'');
CeL.assert([(new CeL.quantity('4cm^2')).toString(),'4 cm²'],'');
CeL.assert([(new CeL.quantity('4cm2')).toString(),'4 cm²'],'');
CeL.assert([(new CeL.quantity('四千五百六十七公尺')).toString(),'4567 m'],'');
CeL.assert([(new CeL.quantity('54公尺')).toString(),'54 m'],'');
CeL.assert([(new CeL.quantity('5公尺')).multiple(4).toString(),'20 m'],'');
CeL.assert([(new CeL.quantity('54cm')).multiple('8.5公尺').toString(),'4.59 m²'],'');
CeL.assert([(new CeL.quantity('54cm')).multiple('8.5公尺').toString('繁體中文'),'4.59平方公尺'],'');
CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('a').toString(),'5 a'],'');
CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('ha').toString(),'0.05 ha'],'');
CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('a').toString('繁體中文'),'5 公畝'],'');


°C
廿分鐘
三磅

 
 * </code>
 */

'use strict';
if (typeof CeL === 'function')
	CeL.run({
		name : 'data.quantity',
		// data.pair
		// |data.native.
		require : 'data.|data.numeral.',
		code : function(library_namespace) {

			/**
			 * 
			 * @param {RegExp}pattern
			 * @param {String}flag
			 * 
			 * @returns {RegExp}
			 */
			function convert_pattern(pattern, flag) {
				return new RegExp(
						pattern.source.replace(/N/, PATTERN_NUMBER_SOURCE)
								.replace(/S/, SUPERSCRIPT_NUMBER), flag);
			}

			// ---------------------------------------------------------------------//
			// 定義基本常數。

			var SUPERSCRIPT_NUMBER =
			// @ data.native
			// Number.prototype.to_super.digits,
			'⁰¹²³⁴⁵⁶⁷⁸⁹',

			//
			PATTERN_NUMBER_SOURCE =
			// /[+\-]?(?:\d+(?:\.\d+)?|\.\d+)(?:E[+\-]?\d+)?/i
			/[+\-]?(?:\d+(?:\.\d+)?|\.\d+)/i.source,

			// [ , number, units, 分母 units ]
			PATTERN_QUANTITY = convert_pattern(
			// /^\s*(N)\s*([^\/\d][^\/]*)(?:\/(.+))?$/
			/^\s*(N)\s*([^\d\s\^].*)$/, 'i'),

			// kg/(m·s2) and kg·m−1·s−2 are acceptable, but kg/m/s2 is ambiguous
			// and unacceptable.
			PATTERN_UNIT = convert_pattern(
			// [ , unit symbol, exponent, exponent, superscript exponent ]
			/([^\d\s\^⋅·×()]+)([+\-]?\d|\^\(?([+\-]?\d)\)?|([⁺⁻]?[S]))?/
			//
			, 'gi'),

			// exponent_to_prefix.pair[6] === 'M'
			exponent_to_prefix = new library_namespace.pair(
			// https://en.wikipedia.org/wiki/Metric_prefix
			'24=Y,21=Z,18=E,15=P,12=T,9=G,6=M,3=k,2=h,1=da,'
			//
			+ '-1=d,-2=c,-3=m,-6=µ,-9=n,-12=p,-15=f,-18=a,-21=z,-24=y'),
			// 數量級
			exponent_of = exponent_to_prefix.clone().reverse({
				is_number : true
			}),
			//
			prefix_pattern = new RegExp('^\\s*(' + exponent_of.keys.join('|')
					+ ')([^\\s]+)$'),

			// prefix name. prefix_name.pair.M === 'mega'.
			prefix_name = new library_namespace.pair(
			//
			'Y=yotta,Z=zetta,E=exa,P=peta,T=tera,'
			//
			+ 'G=giga,M=mega,k=kilo,h=hecto,da=deca,'
			//
			+ 'd=deci,c=centi,m=milli,µ=micro,n=nano,'
			//
			+ 'p=pico,f=femto,a=atto,z=zepto,y=yocto');

			// ---------------------------------------------------------------------//
			// 初始調整並規範基本常數。

			// ---------------------------------------------------------------------//
			// private 工具函數。

			// Prefixes may not be used in combination.
			function parse_prefix(unit) {
				library_namespace.debug('parse_prefix(' + unit + ')', 3);
				var matched = String(unit).match(prefix_pattern);
				// [ unit symbol, prefix exponent ]
				return matched ? [ matched[2], exponent_of.pair[matched[1]] ]
						: [ unit, 0 ];
			}

			// 度量衡單位 : 數量級 (prefix exponent / integer powers of 10)
			// 度量衡單位 : [ 數量級, 單位數量級 (unit power) ]
			// e.g., m : 0 → m
			// e.g., m : 3 → km
			// e.g., m : [ , 2 ] → m^2
			// e.g., m : [ 3 , 2 ] → km^2
			function multiple_unit(quantity, unit_symbol, prefix_exponent,
					unit_power) {

				if (unit_power === '' || isNaN(unit_power))
					unit_power = 1;

				var units = quantity.units;
				if (!units)
					quantity.units = units = Object.create(null);

				if (unit_symbol in units) {
					var array = units[unit_symbol];
					if (!Array.isArray(array))
						// assert: !isNaN(units[unit_symbol])
						units[unit_symbol] = array = [ array, 1 ];
					// 若是 Array，則使用原 Array。

					if (prefix_exponent !== array[0]) {
						// km^3 * cm^-2
						// = 10^(3*3 - (-2)*2) m^(3 + -2)
						// = 10^13 m
						// = 10 Tm
						prefix_exponent = array[0] * array[1] + unit_power
								* (prefix_exponent || 0);
						array[1] += unit_power;
						// The "non-three" prefixes (hecto-, deca-, deci-, and
						// centi-) are however more commonly used for everyday
						// purposes than in science.
						if (array[0] = prefix_exponent / 3 / array[1] | 0)
							prefix_exponent -= 3 * array[0] * array[1];
						// assert: Math.abs(prefix_exponent) = 0, 1, 2

						// 微調：盡量取到不會 overflow 或 underflow 的 prefix。
						if (prefix_exponent < 0) {
							if (Math.abs(quantity.amount) < 1)
								array[0]--, prefix_exponent += 3;

						} else if (prefix_exponent > 0
								&& Math.abs(quantity.amount) > 1e6
								&& quantity.amount % 10 === 0)
							array[0]++, prefix_exponent -= 3;

						// 把剩餘乘進 quantity.amount。
						if (prefix_exponent)
							quantity.amount *= Math.pow(10, prefix_exponent);
						array[0] *= 3;

					} else
						// e.g., km * km = km^2
						array[1] += unit_power;

					if (!array[1])
						delete units[unit_symbol];
					else if (array[1] === 1)
						units[unit_symbol] = array[0];

				} else
					units[unit_symbol] = unit_power !== 1 ? [ prefix_exponent,
							unit_power ] : prefix_exponent;

				return units;
			}

			function parse_unit(unit, quantity, is_division) {
				library_namespace.debug('parse_unit(' + unit + ')', 2);
				if (library_namespace.is_Object(unit))
					return unit;

				var matched = String(unit).match(/([^\/]*)\/([^\/]+)/);
				if (matched)
					return Object.assign(parse_unit(matched[1], quantity),
							parse_unit(matched[2], quantity, true));

				is_division = is_division ? -1 : 1;
				// var units = Object.create(null);
				// reset pattern
				PATTERN_UNIT.lastIndex = 0;
				while (matched = PATTERN_UNIT.exec(unit)) {
					library_namespace.debug('parse_unit: [' + matched + ']', 3);
					if (matched[4])
						matched[2] = matched[4].length > 1 ? (matched[4]
								.charAt(0) === '⁻' ? 1 : -1)
								* SUPERSCRIPT_NUMBER.indexOf(matched[4]
										.charAt(1)) : SUPERSCRIPT_NUMBER
								.indexOf(matched[4]);
					// return [ unit symbol, prefix exponent ]
					matched[1] = parse_prefix(normalize_name(matched[1]));
					multiple_unit(quantity, matched[1][0], matched[1][1],
							matched[3] || matched[2]);
				}

				return quantity.units;
			}

			function unit_to_String(unit_symbol, prefix_exponent, unit_power) {
				if (Array.isArray(prefix_exponent)
				// 處理 [ 數量級, 單位數量級 (unit power) ]
				&& unit_power === undefined) {
					unit_power = prefix_exponent[1];
					prefix_exponent = prefix_exponent[0];
				}

				var unit = (prefix_exponent ?
				//
				exponent_to_prefix.pair[prefix_exponent]
						|| ('10^' + prefix_exponent) : '')
						+ unit_symbol;
				if (unit_power) {
					if (unit_power < 0)
						unit += '⁻', unit_power = -unit_power;
					unit += SUPERSCRIPT_NUMBER[unit_power];
				}

				return unit;
			}

			var unit_name_all, unit_name = {

				// 繁體中文
				'cmn-Hant-TW' : {
					公里 : 'km',
					公尺 : 'm',
					公分 : 'cm',
					厘米 : 'cm',
					公釐 : 'mm',
					毫米 : 'mm'
				},

				// https://zh.wikipedia.org/wiki/%E5%B8%82%E5%88%B6
				'市制' : {
					市尺 : '1/3m'
				},

				// https://zh.wikipedia.org/wiki/%E5%8F%B0%E5%88%B6
				'臺制' : {
					臺尺 : '10/33m'
				},

				// 英制單位
				'imperial' : {},

				// default, 公制
				'' : {
					'㎝' : 'cm',
					'㎠' : 'cm^2',
					'㎤' : 'cm^3',

					'㎜' : 'mm',
					'㎟' : 'mm^2',
					'㎣' : 'mm^3'
				}
			};

			// normalize unit name
			function normalize_name(unit, locale) {
				var matched = unit.match(/^([平立])方(.+)$/),
				//
				prefix_exponent, unit_power;

				if (matched) {
					unit_power = matched[1] === '平' ? 2 : 3;
					unit = matched[2];
				}

				if (!unit_name_all) {
					// initialization.
					unit_name_all = Object.create(null);
					for ( var l in unit_name)
						Object.assign(unit_name_all, unit_name[l]);
				}

				// TODO
				if (false)
					if (unit in unit_name_all)
						return [ unit_name_all[unit],
						//
						prefix_exponent, unit_power ];

				if (unit in unit_name_all)
					return unit_name_all[unit];

				return unit;
			}

			// ---------------------------------------------------------------------//
			// 定義基本常數。

			function Quantity(amount, units, options) {
				if (!units && isNaN(amount)) {
					units = String(amount)
					// 先期處理。
					.replace(/^\s*[〇一二三四五六七八九十百千萬億兆]+/,
					//
					library_namespace.from_Chinese_numeral);

					// e.g., new Quantity('2m', null, { type : 'length' })
					if (units = units.match(PATTERN_QUANTITY)) {
						amount = units[1];
						units = units[2];
					}
				}

				// 數值/量值的大小 / number / amount/count of base unit /
				// numerical value / factor
				// {Number} or [{Number}積]
				if (typeof amount === 'string' && !isNaN(amount))
					amount = +amount;
				this.amount = amount;

				// unit symbol
				this.units = parse_unit(units, this);

				if (options) {
					// 度量衡單位分類 type. e.g., 'length' (長度)
					if (options.type)
						this.type = options.type;

					// 有效數字/the appropriate number of significant
					// figures
					if ('significant_figures' in options)
						this.significant_figures
						//
						= options.significant_figures | 0;
				}
			}

			// 數值單位換算。
			function convert_to(units) {
				;
			}

			function multiple(quantity) {
				if (quantity)
					if (isNaN(quantity)) {
						if (quantity.constructor !== Quantity)
							quantity = new Quantity(quantity);
						this.amount *= quantity.amount;
						var units = quantity.units;
						for ( var unit in units)
							multiple_unit(this, unit, units[unit]);
					} else
						this.amount *= quantity;
				else
					this.amount = 0;
				return this;
			}

			// https://en.wikipedia.org/wiki/International_System_of_Units#Unit_symbols_and_the_values_of_quantities
			// The value of a quantity is written as a number followed
			// by a space
			// (representing a multiplication sign) and a unit symbol
			function to_String(locale) {
				var result = [], units = this.units;
				for ( var unit in units)
					result.push(unit_to_String(unit, units[unit]));
				// intervening space ' ', dot operator '⋅'.
				return this.amount + ' ' + result.join('⋅');
			}

			// http://en.wikipedia.org/wiki/SI_base_unit
			// http://en.wikipedia.org/wiki/International_System_of_Units#Units_and_prefixes
			// http://en.wikipedia.org/wiki/List_of_thermodynamic_properties
			// Quantity.type_of('m')==='length';
			function type_of(unit) {
				;
			}

			function name_of(unit, locale) {
				;
			}

			// Quantity.multiple(Quantity, Quantity);

			function parse_factor() {
				;
			}

			// 同一個物理量的兩種不同的單位之間，是靠著轉換因子（conversion
			// factor）從一個單位轉換到另一個單位。例如，1 in = 2.54
			// cm，注意到在這裡「2.54 cm/in」是轉換因子，不具有因次，其數值等於1。
			parse_factor('min=60s,hr=60min,a=100m2,Newton=kg*m/s2');

			// 常數/constants/factors
			var constants = {
				PI : Math.PI,
				c : 299792458
			};

			// ---------------------------------------------------------------------//
			// 應用功能。

			// ---------------------------------------------------------------------//
			// 網頁應用功能。

			// ---------------------------------------------------------------------//
			// export.

			library_namespace.set_method(Quantity, {
				type_of : type_of,
				name_of : name_of,
				parse_factor : parse_factor,
				constants : constants
			});

			library_namespace.set_method(Quantity.prototype, {
				convert_to : convert_to,
				multiple : multiple
			});
			Quantity.prototype.toString = to_String;

			return (Quantity// JSDT:_module_
			);
		}

	});
